package com.onadake.web.service;

import java.util.HashSet;
import java.util.Set;

import javax.annotation.PostConstruct;

import org.apache.commons.lang3.builder.ToStringBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.onadake.dao.EntityDao;
import com.onadake.domain.Entity;
import com.onadake.domain.EntityType;
import com.onadake.domain.query.Criterium;
import com.onadake.utils.Dates;
import com.onadake.utils.ValidationUtils;
import com.onadake.web.domain.UserFilterColumns;
import com.onadake.web.domain.UserProfile;

/**
 * Implementation of user service which is connected to the entity dao.
 * 
 * @author radek.hecl
 *
 */
@Service
public class EntityDaoUserService implements UserService {

    /**
     * Builder object.
     */
    public static class Builder {

        /**
         * Entity dao.
         */
        private EntityDao entityDao;

        /**
         * Sets entity dao.
         * 
         * @param entityDao entity dao
         * @return this instance
         */
        public Builder setEntityDao(EntityDao entityDao) {
            this.entityDao = entityDao;
            return this;
        }

        /**
         * Builds the result object.
         * 
         * @return created object
         */
        public EntityDaoUserService build() {
            return new EntityDaoUserService(this);
        }
    }

    /**
     * Entity dao.
     */
    @Autowired
    private EntityDao entityDao;

    /**
     * Constructor to allow frameworks create new instances.
     */
    @SuppressWarnings("unused")
    private EntityDaoUserService() {
    }

    /**
     * Creates new instance.
     * 
     * @param builder builder object
     */
    public EntityDaoUserService(Builder builder) {
        entityDao = builder.entityDao;
        guardInvariants();
    }

    /**
     * Guards this object to be consistent. Throws exception if this is not the case.
     */
    @PostConstruct
    private void guardInvariants() {
        ValidationUtils.guardNotNull(entityDao, "entityDao cannot be null");
    }

    @Override
    @Transactional(readOnly = true)
    public long countUsers(Set<Criterium<UserFilterColumns>> criteria) {
        Set<Criterium<String>> daoCriteria = new HashSet<>();
        for (Criterium<UserFilterColumns> crit : criteria) {
            String column = null;
            switch (crit.getColumn()) {
            case E_MAIL:
                column = "join-users.email";
                break;
            default:
                throw new IllegalArgumentException("unsupported column, please implement me: column = " + column);
            }
            daoCriteria.add(new Criterium.Builder<String>().
                    setColumn(column).
                    setOperator(crit.getOperator()).
                    setValue(crit.getValue()).
                    build());
        }
        return entityDao.countEntities(daoCriteria);
    }

    @Override
    @Transactional
    public void insertUser(UserProfile profile, String password) {
        Entity entity = new Entity.Builder().
                setCode("US0000000000000002"). // TODO - make code
                setName(profile.getKanjiName()).
                setUtcCreatedTimestamp(Dates.now()).
                setType(EntityType.USER).
                addProfile("profile.kanjiName", profile.getKanjiName()).
                addProfile("profile.kanaName", profile.getKanaName()).
                addProfile("profile.birthDate", Dates.getFormattedDate(profile.getBirthDate(), "yyyy/MM/dd")).
                addJoin("users.email", profile.getEmail()).
                addJoin("users.password_hash", password). // TODO - make hash
                addJoin("users.enabled", true).
                build();
        entityDao.insertEntity(entity);
    }

    @Override
    public String toString() {
        return ToStringBuilder.reflectionToString(this);
    }

}
